﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using HtmlAgilityPack;
using System.IO;
using System.Data;
using System.Text.RegularExpressions;

namespace DataExtractionLanguage
{
    public class FromClause
    {
        /// <summary>
        /// Gets or sets the source path list of the FromClause.
        /// </summary>
        public SourceList Source { get; set; }

        /// <summary>
        /// Gets or sets all join clauses which come after the FromClause.
        /// </summary>
        public List<JoinClause> Joins { get; set; }

        internal List<SourceList> Sources { get; private set; }

        /// <summary>
        /// Returns SourceList by its alias.
        /// </summary>
        /// <param name="alias">Alias of the SourceList to be returned.</param>
        /// <returns>SourceList object if it is existed. curSrcLst value, otherwise.</returns>
        internal SourceList GetSourceByAlias(string alias)
        {
            if (Source.Alias == alias)
                return Source;

            foreach (JoinClause joinClause in Joins)
                if (joinClause.Source.Alias == alias)
                    return joinClause.Source;

            return curSrcLst;
        }

        public FromClause()
        {
            Joins = new List<JoinClause>();
            Sources = new List<SourceList>();
        }

        #region First Data Extraction
        private SourceList curSrcLst;
        public void FirstDataExtraction(List<Column> actualColumn)
        {
            foreach (SourceList srcLst in Sources)
            {
                srcLst.BuildColumnResult(actualColumn);
                srcLst.InitHtmlDocument();
                srcLst.ExtractData();
                srcLst.CreateResultTable();
            }

            for (int srcLstIndex = 0; srcLstIndex < Sources.Count; srcLstIndex++)
            {
                curSrcLst = Sources[srcLstIndex];
                PopulateTableResult();
            }
        }

        private void PopulateTableResult()
        {
            DataRow newRow = null;
            for (int rowIndex = 0; rowIndex < curSrcLst.RowCount; rowIndex++)
            {
                newRow = curSrcLst.Result.NewRow();
                PopulateRowValues(newRow, rowIndex);
                curSrcLst.Result.Rows.Add(newRow);
            }
        }

        private void PopulateRowValues(DataRow row, int rowIndex)
        {
            foreach (KeyValuePair<Column, HtmlNodeCollection> colRst in curSrcLst.ColumnsResult)
                row[colRst.Key.Alias] = ExtractCellValue(rowIndex, colRst.Key);
        }

        private object ExtractCellValue(int rowIndex, Column column)
        {
            // If column result's values less than row index we have to return null.
            if (rowIndex >= curSrcLst.ColumnsResult[column].Count)
                return null;

            // Get the HtmlNode and evaluate it's value.
            HtmlNode node = curSrcLst.ColumnsResult[column][rowIndex];
            if (node != null)
                return EvaluateProperty(rowIndex, node, column.Property);

            return null;
        }

        private object EvaluateProperty(int rowIndex, HtmlNode htmlNode, PropertyMember propMember)
        {
            HtmlNodeCollection nodes = null;
            string retVal = string.Empty;

            // 1. Evalute property.
            switch (propMember.Property)
            {
                case TokenTypeEnum.Text:
                    retVal = htmlNode.InnerText;
                    break;

                case TokenTypeEnum.GetAttribute:
                    retVal = htmlNode.GetAttributeValue(propMember.PropertyParameter1, "");
                    break;

                case TokenTypeEnum.CountOf:
                    if (htmlNode.HasChildNodes)
                    {
                        nodes = htmlNode.SelectNodes(propMember.PropertyParameter1);
                        if (nodes != null)
                            return nodes.Count;
                        else
                            return 0;
                    }
                    else
                        return 0;

                case TokenTypeEnum.ChildCount:
                    return htmlNode.DescendantNodes().Count();
            }

            // 2. Evalute function.
            Function funcPtr = propMember.Function;
            while (funcPtr != null)
            {
                if (funcPtr.FunctionType == TokenTypeEnum.SubString)
                {
                    ExpressionTerm startIndex = EvaluateExpressionTerm(funcPtr.StartIndexExp, rowIndex);
                    if (funcPtr.LengthExp == null)
                        retVal = retVal.Substring(Convert.ToInt32(startIndex.Value));
                    else
                    {
                        ExpressionTerm length = EvaluateExpressionTerm(funcPtr.LengthExp, rowIndex);
                        retVal = retVal.Substring(Convert.ToInt32(startIndex.Value), Convert.ToInt32(length.Value));
                    }
                }
                else if (funcPtr.FunctionType == TokenTypeEnum.Length)
                    return retVal.Length;
                else if (funcPtr.FunctionType == TokenTypeEnum.Like)
                {
                    ExpressionTerm stringExp = EvaluateExpressionTerm(funcPtr.StringExp, rowIndex);
                    return retVal.Contains(stringExp.Value.ToString());
                }
                else if (funcPtr.FunctionType == TokenTypeEnum.IndexOf)
                {
                    ExpressionTerm stringExp = EvaluateExpressionTerm(funcPtr.StringExp, rowIndex);
                    return retVal.IndexOf(stringExp.Value.ToString());
                }
                else if (funcPtr.FunctionType == TokenTypeEnum.Trim)
                {
                    retVal = retVal.Trim();
                }
                else if (funcPtr.FunctionType == TokenTypeEnum.Match)
                {
                    ExpressionTerm stringExp = EvaluateExpressionTerm(funcPtr.StringExp, rowIndex);
                    Match match = Regex.Match(retVal, stringExp.Value.ToString());
                    if (match.Success)
                        retVal = match.Value;
                    else
                        retVal = string.Empty;
                }

                funcPtr = funcPtr.NextFunction;
            }

            return retVal;
        }

        private ExpressionTerm EvaluateExpressionTerm(ExpressionTerm exp, int rowIndex)
        {
            ExpressionTerm exp0 = exp.Clone();
            ExpressionTerm curExp = exp0;
            ExpressionTerm prev = null;
            ExpressionTerm prevPrev = null;

            while (curExp != null)
            {
                if (curExp.TermType.In(TokenTypeEnum.String, TokenTypeEnum.Number))
                {
                    prevPrev = prev;
                    prev = curExp;
                    curExp = curExp.Next;
                }
                else if (curExp.TermType == TokenTypeEnum.Term)
                {
                    SourceList srcLst = GetSourceByAlias(curExp.Term.SourceAlias);
                    Column termCol = srcLst.GetColumnByAlias(curExp.Term.ColumnAlias);

                    //object cellValue = FigureOutCellValue(currentRowIndex, termCol, srcLst);
                    HtmlNode node = null;
                    object cellValue = null;
                    if (rowIndex < srcLst.ColumnsResult[termCol].Count)
                    {
                        node = srcLst.ColumnsResult[termCol][rowIndex];
                        cellValue = EvaluateProperty(rowIndex, node, curExp.Term.Property);
                    }

                    if (cellValue != null)
                    {
                        if (cellValue is int)
                            curExp.TermType = TokenTypeEnum.Number;
                        else if (cellValue is string)
                            curExp.TermType = TokenTypeEnum.String;
                        else if (cellValue is bool)
                            curExp.TermType = TokenTypeEnum.Bool;

                        curExp.Value = cellValue;
                    }
                    else
                        throw new Exception("null value");

                    prevPrev = prev;
                    prev = curExp;
                    curExp = curExp.Next;
                }
                else if (curExp.TermType == TokenTypeEnum.OpNot)
                {
                    if (prev == null)
                        throw new Exception("خطأ");

                    if (prev.TermType == TokenTypeEnum.Number)
                    {
                        if (prev.TermType == TokenTypeEnum.OpNot)
                            prev.Value = !Convert.ToBoolean(prev.Value);
                    }

                    prev.Next = curExp.Next;
                    curExp = prev.Next;
                }
                else if (curExp.TermType.In(TokenTypeEnum.OpPlus, TokenTypeEnum.OpMinus, TokenTypeEnum.OpMultiple, TokenTypeEnum.OpDivide, TokenTypeEnum.OpPower, TokenTypeEnum.OpAnd, TokenTypeEnum.OpOr, TokenTypeEnum.OpGreater, TokenTypeEnum.OpGreaterOrEqual, TokenTypeEnum.OpLess, TokenTypeEnum.OpLessOrEqual, TokenTypeEnum.OpEqualEqual, TokenTypeEnum.OpNotEqual))
                {
                    if (prevPrev.TermType == TokenTypeEnum.Number && prev.TermType == TokenTypeEnum.Number)
                    {
                        if (curExp.TermType == TokenTypeEnum.OpPlus)
                            prevPrev.Value = Convert.ToInt32(prevPrev.Value) + Convert.ToInt32(prev.Value);
                        else if (curExp.TermType == TokenTypeEnum.OpMinus)
                            prevPrev.Value = Convert.ToInt32(prevPrev.Value) - Convert.ToInt32(prev.Value);
                        else if (curExp.TermType == TokenTypeEnum.OpMultiple)
                            prevPrev.Value = Convert.ToInt32(prevPrev.Value) * Convert.ToInt32(prev.Value);
                        else if (curExp.TermType == TokenTypeEnum.OpDivide)
                        {
                            if (Convert.ToInt32(prev.Value) == 0)
                                throw new Exception("خطأ");

                            prevPrev.Value = Convert.ToInt32(prevPrev.Value) / Convert.ToInt32(prev.Value);
                        }
                        else if (curExp.TermType == TokenTypeEnum.OpOr)
                            if (Convert.ToInt32(prevPrev.Value) == 0 && Convert.ToInt32(prev.Value) == 0)
                                prevPrev.Value = 0;
                            else
                                prevPrev.Value = 1;

                        else if (curExp.TermType == TokenTypeEnum.OpAnd)
                            if (Convert.ToInt32(prevPrev.Value) != 0 && Convert.ToInt32(prev.Value) != 0)
                                prevPrev.Value = 1;
                            else
                                prevPrev.Value = 0;

                        else if (curExp.TermType == TokenTypeEnum.OpPower)
                            prevPrev.Value = Convert.ToDecimal(Math.Pow(Convert.ToDouble(prevPrev.Value), Convert.ToDouble(prev.Value)));
                        else if (curExp.TermType == TokenTypeEnum.OpEqualEqual)
                            prevPrev.Value = Convert.ToBoolean(prevPrev.Value == prev.Value);
                        else if (curExp.TermType == TokenTypeEnum.OpNotEqual)
                            prevPrev.Value = Convert.ToBoolean(prevPrev.Value != prev.Value);
                        else if (curExp.TermType == TokenTypeEnum.OpGreater)
                            prevPrev.Value = Convert.ToBoolean(Convert.ToInt32(prevPrev.Value) > Convert.ToInt32(prev.Value));
                        else if (curExp.TermType == TokenTypeEnum.OpGreaterOrEqual)
                            prevPrev.Value = Convert.ToDecimal(Convert.ToInt32(prevPrev.Value) >= Convert.ToInt32(prev.Value));
                        else if (curExp.TermType == TokenTypeEnum.OpLess)
                            prevPrev.Value = Convert.ToDecimal(Convert.ToInt32(prevPrev.Value) < Convert.ToInt32(prev.Value));
                        else if (curExp.TermType == TokenTypeEnum.OpLessOrEqual)
                            prevPrev.Value = Convert.ToDecimal(Convert.ToInt32(prevPrev.Value) <= Convert.ToInt32(prev.Value));
                    }
                    else if (prevPrev.TermType == TokenTypeEnum.String && prev.TermType == TokenTypeEnum.String)
                    {
                        if (curExp.TermType == TokenTypeEnum.OpEqualEqual)
                        {
                            prevPrev.Value = Convert.ToBoolean(prevPrev.Value.ToString() == prev.Value.ToString());
                            prevPrev.TermType = TokenTypeEnum.Bool;
                        }
                        else if (curExp.TermType == TokenTypeEnum.OpPlus)
                            prevPrev.Value = prevPrev.Value.ToString() + prev.Value.ToString();
                    }
                    else if (prevPrev.TermType == TokenTypeEnum.Bool && prev.TermType == TokenTypeEnum.Bool)
                    {
                        if (curExp.TermType == TokenTypeEnum.OpAnd)
                        {
                            prevPrev.Value = Convert.ToBoolean(prevPrev.Value) & Convert.ToBoolean(prev.Value);
                            prevPrev.TermType = TokenTypeEnum.Bool;
                        }
                        else if (curExp.TermType == TokenTypeEnum.OpOr)
                        {
                            prevPrev.Value = Convert.ToBoolean(prevPrev.Value) | Convert.ToBoolean(prev.Value);
                            prevPrev.TermType = TokenTypeEnum.Bool;
                        }
                    }

                    ExpressionTerm tmp = exp0;
                    if (prevPrev == exp0)
                        tmp = null;
                    else
                        while (tmp.Next != prevPrev)
                            tmp = tmp.Next;

                    prevPrev.Next = curExp.Next;
                    prev = prevPrev;
                    prevPrev = tmp;
                    curExp = prev.Next;
                }
            }

            return exp0;
        }
        #endregion

        public DataTable Select()
        {
            SourceList resultSrcLst = Source;

            if (Joins.Count > 0)
                foreach (JoinClause joinClause in Joins)
                {
                    JoinPart[] joinParties = GetJoinParts(joinClause.LinkageExp);
                    foreach (JoinPart joinPart in joinParties)
                        resultSrcLst = joinPart.ApplyJoin(joinClause, this);
                }

            return resultSrcLst.Result;
        }

        private JoinPart[] GetJoinParts(ExpressionTerm linkageExp)
        {
            ExpressionTerm exp0 = linkageExp.Clone();
            ExpressionTerm curExp = exp0;

            List<JoinPart> parts = new List<JoinPart>();
            JoinPart jp = null;

            while (curExp != null)
            {
                jp = new JoinPart();
                while (!curExp.TermType.In(TokenTypeEnum.OpEqualEqual, TokenTypeEnum.OpNotEqual, TokenTypeEnum.OpGreater, TokenTypeEnum.OpGreaterOrEqual, TokenTypeEnum.OpLess, TokenTypeEnum.OpLessOrEqual))
                {
                    jp.AddPart(GetSourceByAlias(curExp.Term.SourceAlias));
                    curExp = curExp.Next;
                }
                parts.Add(jp);

                curExp = curExp.Next;
            }

            return parts.ToArray();
        }
    }
}